在我們開始動手寫程式去實作圖書館管理系統之前,首先可以先來分析一下,在這個圖書館的管理系統中,我們想要提供什麼樣的功能。
像是圖書館管理系統顧名思義,就是用來管理「書」的,所以我們針對「書」這個資源,就可以去實作他的 CRUD 四大基本操作,也就是「新增一本書」、「查詢某一本書」、「更新某本書的資訊」、「刪除某一本書」。
因此下面就會分別來介紹,要如何在 Spring Boot 中設計和實作出「書」的 CRUD 四個功能,完成一個簡易的圖書館管理系統。
在設計資料庫 table 時,因為是針對「書本」這個資源來設計,因此我們可以設計一個 book table,去儲存書本的相關資訊。以下是創建 book table 的 SQL 語法:
CREATE TABLE book
(
book_id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(128) NOT NULL,
author VARCHAR(32) NOT NULL,
image_url VARCHAR(256) NOT NULL,
price INT NOT NULL,
published_date TIMESTAMP NOT NULL,
created_date TIMESTAMP NOT NULL,
last_modified_date TIMESTAMP NOT NULL
);
其中各個欄位的含義如下:
通常在實作基礎的 CRUD 功能時,建議可以先從「查詢功能」開始實作,因此我們就先從「查詢一本書」這個功能開始實作。
首先我們可以先在 BookController 中,添加如下的程式:
@GetMapping("/books/{bookId}")
public ResponseEntity<Book> getBook(@PathVariable Integer bookId) {
Book book = bookService.getBookById(bookId);
if (book != null) {
return ResponseEntity.status(HttpStatus.OK).body(book);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
}
補充:由於程式的篇幅過長,因此本文中只會擷取部分重點來介紹,完整的程式碼可以前往 GitHub springboot-library 查看。
在 BookController 中,因為他是屬於 Controller 層,所以他會使用 @GetMapping
,去接住前端放在 url 路徑中的 bookId 的參數。
而在接到 bookId 的值之後,BookController 就會直接往後傳給 BookService 去做後續處理,即是將商業邏輯寫在 Service 層,讓 Controller 層保持「和前端溝通」的部分。
而當 BookService 返回查詢到的 book 數據之後,BookController 就可以根據「是否有查詢到數據與否」,去決定要返回給前端的 Http status code 為何。
像是在下面的程式中,if
區塊就呈現出「當 book 不為 null 時,就返回 200 OK 的 Http status code 給前端,並且把 book 數據放在 response body 中」的資訊。而在 else
區塊中,則是呈現「當 book 為 null 時,就返回 404 Not Found 的 Http status code 給前端」。
if (book != null) {
return ResponseEntity.status(HttpStatus.OK).body(book);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
}
實作完 BookController 之後,接著我們往下實作 BookService。
當 BookService 收到 Controller 傳過來的 bookId 之後,因為這裡的邏輯比較簡單,沒有太複雜的商業邏輯要處理,因此 BookService 就只要直接去 call BookDao 的方法,交由 Dao 層去資料庫查詢數據即可。
public Book getBookById(Integer bookId) {
return bookDao.getBookById(bookId);
}
而在 BookDao 這裡,因為他是 Dao 層,負責和「資料庫」溝通,因此就可以在這裡使用 Spring JDBC 的 query()
方法,在資料庫中執行 SELECT SQL,去查詢這一筆 bookId 的數據出來。
public Book getBookById(Integer bookId) {
String sql = "SELECT book_id, title, author, image_url, price, published_date, created_date, last_modified_date " +
"FROM book WHERE book_id = :bookId";
Map<String, Object> map = new HashMap<>();
map.put("bookId", bookId);
List<Book> bookList = namedParameterJdbcTemplate.query(sql, map, new BookRowMapper());
if (bookList.size() > 0) {
return bookList.get(0);
} else {
return null;
}
}
因此到這邊,「查詢某一本書」的功能就實作完畢了!
因為目前 book table 中沒有任何數據,因此在測試「查詢某一本書」的功能之前,我們需要先在 IntelliJ 的 console 中執行下列的 SQL 語法,手動插入一筆數據到 book table 中。
INSERT INTO book (title, author, image_url, price, published_date, created_date, last_modified_date) VALUES ('先問,為什麼?:顛覆慣性思考的黃金圈理論,啟動你的感召領導力', '賽門‧西奈克', 'https://im1.book.com.tw/image/getImage?i=https://www.books.com.tw/img/001/092/65/0010926506.jpg', 331, '2018-05-23 00:00:00', '2023-10-14 02:42:02', '2023-10-14 02:42:02');
插入這本書的數據之後,接著就可以在 API Tester 中,填上以下的參數設定,來測試「查詢某一本書」的功能:
填寫完成之後,就可以按下 Send 鍵進行測試,結果如下:
您好,我想請問實作「新增一本書」這一個部分的功能
我的 API Tester 顯示 400 Bad Request
Controller Service Dao 以及 Request 的程式碼我確認都沒有問題
Spring Console 的報錯為:
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type java.util.Date
from String "2019-06-01 00:00:00": not a valid representation (error: Failed to parse Date value '2019-06-01 00:00:00': Cannot parse date "2019-06-01 00:00:00": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null))]
您的文章曾經提過
前端傳給後端的參數名稱不同、或是說請求的格式有問題,都是可以被歸類在 400 這個 status code,所以以後只要看到 400,通常第一件事,就是回頭檢查一下是不是請求的參數寫錯了
加上報錯好像也有提到格式不匹配的問題,請問是否是
這一段參數設定的
{
"title": "原子習慣:細微改變帶來巨大成就的實證法則",
"author": "詹姆斯‧克利爾",
"imageUrl": "https://im1.book.com.tw/image/getImage?i=https://www.books.com.tw/img/001/082/25/0010822522.jpg",
"price": 260,
"publishedDate": "2019-06-01 00:00:00"
}
"publishedDate": "2019-06-01 00:00:00" 這個部分的設定有問題呢?
不得不說 ChatGPT 還真的是好用xD ,只不過你得先有耐心地將你的問題統整好後,完整地敘述給它聽。
以下為這個問題解決的方法,如果您有遇到同樣的問題,可以參考看看。
在BookRequest 以及 Book 這兩個Class中的
private Date publishedDate;
加上
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
變成
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date publishedDate;
透過這個註解来指定日期的解析格式
我的部分是加入這個註解之後,API Tester 測試就是 201 成功了
資料庫也順利新增一筆資料了!
謝謝分享
後來發現作者的github程式碼中有把相關的設定加入到application.properties裡面,就無需這個annotation了
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myjdbc?serverTimezone=Asia/Taipei&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=springboot
spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
哇我竟然漏掉這一串討論了,抱歉抱歉😭
時間格式之所以會是 yyyy-MM-dd HH:mm:ss,確實是因為我在 application.properties 中偷加了下面兩行程式所導致的
spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
抱歉我應該把這個寫進去文章裡面的🥹,感謝大家的回覆!文章也已經修正完畢了!
完整的把所有文章看過了,感謝你的系列文章,收穫良多,字詞簡潔明瞭,解釋通暢直白,感激不盡!!
噢噢噢有幫助到你就好!感謝感謝~